﻿using System;
using System.Collections.Generic;
using System.Linq;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel
{
    /// <summary>
    /// Implements an undoable command for creating a new dynamic mark.
    /// </summary>
    /// <remarks>
    /// Input Parameters:
    /// <list type="table">
    /// <listheader>
    /// <term>
    ///     Name
    /// </term>
    /// <description>
    ///     Description
    /// </description>
    /// </listheader>
    /// <item>
    /// <term>
    ///     <see cref="Degree"/> (<see cref="Byte"/>)
    /// </term>
    /// <description>
    ///     The degree of the mark (default is 1).
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     <see cref="MarkType"/> (<see cref="Elements.CWNotation.DynamicMarkType"/>)
    /// </term>
    /// <description>
    ///     The type of the mark (default is <see cref="Elements.CWNotation.DynamicMarkType.Piano"/>).
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     <see cref="Symbol"/> (<see cref="Elements.CWNotation.DurationalSymbol"/>)
    /// </term>
    /// <description>
    ///     The symbol to which the mark will be attached.
    /// </description>
    /// </item>
    /// </list>
    /// </remarks>
    public class AddDynamicMarkCommand : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="AddDynamicMarkCommand"/> class.
        /// </summary>
        /// <param name="undoStack">The undo command stack</param>
        /// <param name="redoStack">The redo command stack</param>
        public AddDynamicMarkCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<byte>("Degree", ParameterDirection.In);
            AddParameter<Elements.CWNotation.DynamicMarkType>("MarkType", ParameterDirection.In);
            AddParameter<Elements.CWNotation.DurationalSymbol>("Symbol", ParameterDirection.In);

            createdMarks = new List<Elements.CWNotation.DynamicMark>();

            Name = "Create Dynamic Mark";
            Degree = 1;
            MarkType = Elements.CWNotation.DynamicMarkType.Piano;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Processes the affected section.
        /// </summary>
        /// <param name="section">
        /// <para>
        /// Type: <see cref="Elements.MusicSection"/>
        /// </para>
        /// <para>
        /// The section.
        /// </para>
        /// </param>
        /// <param name="staffDescriptor">
        /// <para>
        /// Type: <see cref="Model.Elements.CWNotation.StaffDescriptor"/>
        /// </para>
        /// <para>
        /// The descriptor of the staff to which the mark was inserted.
        /// </para>
        /// </param>
        /// <param name="measure">
        /// <para>
        /// Type: <see cref="Model.Elements.MeasureElement"/>
        /// </para>
        /// <para>
        /// The measure to which the mark was inserted.
        /// </para>
        /// </param>
        private void ProcessAffectedSection(Elements.MusicSection section, Model.Elements.CWNotation.StaffDescriptor staffDescriptor, Model.Elements.MeasureElement measure)
        {
            var modelSection = section.AdaptedElement as Model.Elements.MusicSectionElement;

            if (!modelSection.Parts.Contains(staffDescriptor.FindOwner<Model.Elements.PartDescriptorElement>()))
                return;

            var modelInterior = modelSection.Interior as Model.Elements.CWNotation.MusicSectionInterior;
            var modelSystem = modelInterior.Systems.First(s => s.Measures.Contains(measure));
            var modelStaff = modelSystem.Staves.FirstOrDefault(s => s.Descriptor == staffDescriptor);

            if (modelStaff == null)
                return;

            var interior = section.Interior as Elements.CWNotation.MusicSectionInterior;
            var system = interior.Systems.First(s => s.AdaptedElement == modelSystem);
            var staff = system.Staves.First(s => s.AdaptedElement == modelStaff);

            var modelMark = modelStaff.Attachments.First(a => a is Model.Elements.CWNotation.PresentedAttachment && ((Model.Elements.CWNotation.PresentedAttachment)a).PresentedElement == markElement);
            createdMarks.Add(staff.Attachments.First(a => a.AdaptedElement == modelMark) as Elements.CWNotation.DynamicMark);

            if (modelSection.FindOwner<Model.Elements.PageElement>() != null)
                Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(modelSystem);
            else
                Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(modelSystem);
        }

        #endregion
        
        #region Command Logic

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            var staff = Symbol.FindOwner<Elements.CWNotation.Staff>();
            var modelStaff = (Model.Elements.CWNotation.Staff)staff.AdaptedElement;
            var modelSymbol = (Model.Elements.CWNotation.DurationalSymbol)Symbol.AdaptedElement;
            var measure = modelSymbol.Measure;
            var beat = modelSymbol.Beat;

            markElement = new Model.Elements.DynamicMarkElement(modelSymbol.PresentedElement.PackagePart as Model.Packaging.CompositionPart, beat);
            markElement.Voices.Add(modelSymbol.PresentedElement.Voice);
            markElement.MarkType = (Model.Elements.DynamicMarkType)Enum.Parse(typeof(Model.Elements.DynamicMarkType), MarkType.ToString());
            markElement.Degree = Degree;
            modelSymbol.PresentedElement.FindOwner<Model.Elements.MusicalContentElement>().AddAttachment(markElement, beat);

            var references = measure.FindOwner<Model.Elements.CompositionElement>().GetReferencingPages(measure, measure);
            var project = Symbol.FindOwner<Elements.Project>();
            foreach (var reference in references)
            {
                var score = project.Scores.First(s => s.AdaptedPart == reference.Page.Owner);
                var page = score.Pages.First(p => p.AdaptedPart == reference.Page);
                var section = page.Sections.First(s => s.AdaptedElement == reference.Section) as Elements.MusicSection;

                ProcessAffectedSection(section, modelStaff.Descriptor, measure);
            }

            var composition = project.Compositions.First(c => c.AdaptedElement == measure.FindOwner<Model.Elements.CompositionElement>());
            ProcessAffectedSection(composition.LinearView.Content, modelStaff.Descriptor, measure);

            project.IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            markElement.FindOwner<Model.Elements.MusicalContentElement>().AddAttachment(markElement, markElement.FindOwner<Model.Elements.BeatElement>());

            foreach (var mark in createdMarks)
            {
                var staff = mark.FindOwner<Elements.CWNotation.Staff>();
                var modelStaff = staff.AdaptedElement as Model.Elements.CWNotation.Staff;
                modelStaff.Attachments[
                    modelStaff.Attachments.FindIndex(a => a is Model.Elements.CWNotation.DynamicMark && ((Model.Elements.CWNotation.DynamicMark)a).PresentedElement == markElement)
                    ] = mark.AdaptedElement as Model.Elements.CWNotation.DynamicMark;
                var vmMark = staff.Attachments.First(a => a.AdaptedElement == mark.AdaptedElement);
                staff.Attachments[staff.Attachments.IndexOf(vmMark)] = mark;

                if (modelStaff.FindOwner<Model.Elements.PageElement>() != null)
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(modelStaff.FindOwner<Model.Elements.CWNotation.System>());
                else
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(modelStaff.FindOwner<Model.Elements.CWNotation.System>());
            }

            Symbol.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            markElement.FindOwner<Model.Elements.MusicalContentElement>().RemoveAttachment(markElement);

            foreach (var mark in createdMarks)
            {
                if (mark.FindOwner<Elements.Page>() != null)
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(mark.FindOwner<Elements.CWNotation.System>().AdaptedElement as Model.Elements.CWNotation.System);
                else
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(mark.FindOwner<Elements.CWNotation.System>().AdaptedElement as Model.Elements.CWNotation.System);
            }

            Symbol.FindOwner<Elements.Project>().IsModified = true;
        }

        #endregion

        #region Public Properties
        
        /// <summary>
        /// Gets or sets the degree of the mark.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Byte"/>
        /// </para>
        /// <para>
        /// The degree of the mark. Default value is 1.<br/>
        /// This property wraps the "Degree" command parameter.
        /// </para>
        /// </value>
        public byte Degree
        {
            get { return GetParameterValue<byte>("Degree"); }
            set { SetParameterValue<byte>("Degree", value); }
        }

        /// <summary>
        /// Gets or sets the type of the mark.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.CWNotation.DynamicMarkType"/>
        /// </para>
        /// <para>
        /// The type of the mark. Default value is <see cref="Elements.CWNotation.DynamicMarkType.Piano"/><br/>
        /// This property wraps the "MarkType" command parameter.
        /// </para>
        /// </value>
        public Elements.CWNotation.DynamicMarkType MarkType
        {
            get { return GetParameterValue<Elements.CWNotation.DynamicMarkType>("MarkType"); }
            set { SetParameterValue<Elements.CWNotation.DynamicMarkType>("MarkType", value); }
        }
        
        /// <summary>
        /// Gets or sets the durational symbol to which the mark will be attached.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.CWNotation.DurationalSymbol"/>
        /// </para>
        /// <para>
        /// The durational symbol to which the mark will be attached.<br/>
        /// This property wraps the "Symbol" command paramter.
        /// </para>
        /// </value>
        public Elements.CWNotation.DurationalSymbol Symbol
        {
            get { return GetParameterValue<Elements.CWNotation.DurationalSymbol>("Symbol"); }
            set { SetParameterValue<Elements.CWNotation.DurationalSymbol>("Symbol", value); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the list of created marks.
        /// </summary>
        private List<Elements.CWNotation.DynamicMark> createdMarks;
        /// <summary>
        /// Holds the created dynamic mark element.
        /// </summary>
        private Model.Elements.DynamicMarkElement markElement;

        #endregion
    }
}
